/*
 * Copyright (c) 2007-2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: ExsltStrings.java,v 1.1.2.1 2005/08/01 02:08:48 jeffsuttor Exp $
 */
package com.sun.org.apache.xalan.internal.lib;

import java.util.StringTokenizer;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;

import com.sun.org.apache.xpath.internal.NodeSet;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

/**
 * This class contains EXSLT strings extension functions.
 *
 * It is accessed by specifying a namespace URI as follows:
 * <pre>
 *    xmlns:str="http://exslt.org/strings"
 * </pre>
 * The documentation for each function has been copied from the relevant
 * EXSLT Implementer page.
 *
 * @see <a href="http://www.exslt.org/">EXSLT</a>

 * @xsl.usage general
 */
public class ExsltStrings extends ExsltBase {
    static final String JDK_DEFAULT_DOM = "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl";

    /**
     * The str:align function aligns a string within another string.
     * <p>
     * The first argument gives the target string to be aligned. The second argument gives
     * the padding string within which it is to be aligned.
     * <p>
     * If the target string is shorter than the padding string then a range of characters
     * in the padding string are repaced with those in the target string. Which characters
     * are replaced depends on the value of the third argument, which gives the type of
     * alignment. It can be one of 'left', 'right' or 'center'. If no third argument is
     * given or if it is not one of these values, then it defaults to left alignment.
     * <p>
     * With left alignment, the range of characters replaced by the target string begins
     * with the first character in the padding string. With right alignment, the range of
     * characters replaced by the target string ends with the last character in the padding
     * string. With center alignment, the range of characters replaced by the target string
     * is in the middle of the padding string, such that either the number of unreplaced
     * characters on either side of the range is the same or there is one less on the left
     * than there is on the right.
     * <p>
     * If the target string is longer than the padding string, then it is truncated to be
     * the same length as the padding string and returned.
     *
     * @param targetStr The target string
     * @param paddingStr The padding string
     * @param type The type of alignment
     *
     * @return The string after alignment
     */
    public static String align(String targetStr, String paddingStr, String type) {
        if (targetStr.length() >= paddingStr.length())
            return targetStr.substring(0, paddingStr.length());

        if (type.equals("right")) {
            return paddingStr.substring(0, paddingStr.length() - targetStr.length()) + targetStr;
        } else if (type.equals("center")) {
            int startIndex = (paddingStr.length() - targetStr.length()) / 2;
            return paddingStr.substring(0, startIndex) + targetStr + paddingStr.substring(startIndex + targetStr.length());
        }
        // Default is left
        else {
            return targetStr + paddingStr.substring(targetStr.length());
        }
    }

    /**
     * See above
     */
    public static String align(String targetStr, String paddingStr) {
        return align(targetStr, paddingStr, "left");
    }

    /**
     * The str:concat function takes a node set and returns the concatenation of the
     * string values of the nodes in that node set. If the node set is empty, it returns
     * an empty string.
     *
     * @param nl A node set
     * @return The concatenation of the string values of the nodes in that node set
     */
    public static String concat(NodeList nl) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            String value = toString(node);

            if (value != null && value.length() > 0)
                sb.append(value);
        }

        return sb.toString();
    }

    /**
     * The str:padding function creates a padding string of a certain length.
     * The first argument gives the length of the padding string to be created.
     * The second argument gives a string to be used to create the padding. This
     * string is repeated as many times as is necessary to create a string of the
     * length specified by the first argument; if the string is more than a character
     * long, it may have to be truncated to produce the required length. If no second
     * argument is specified, it defaults to a space (' '). If the second argument is
     * an empty string, str:padding returns an empty string.
     *
     * @param length The length of the padding string to be created
     * @param pattern The string to be used as pattern
     *
     * @return A padding string of the given length
     */
    public static String padding(double length, String pattern) {
        if (pattern == null || pattern.length() == 0)
            return "";

        StringBuffer sb = new StringBuffer();
        int len = (int) length;
        int numAdded = 0;
        int index = 0;
        while (numAdded < len) {
            if (index == pattern.length())
                index = 0;

            sb.append(pattern.charAt(index));
            index++;
            numAdded++;
        }

        return sb.toString();
    }

    /**
     * See above
     */
    public static String padding(double length) {
        return padding(length, " ");
    }

    /**
     * The str:split function splits up a string and returns a node set of token
     * elements, each containing one token from the string.
     * <p>
     * The first argument is the string to be split. The second argument is a pattern
     * string. The string given by the first argument is split at any occurrence of
     * this pattern. For example:
     * <pre>
     * str:split('a, simple, list', ', ') gives the node set consisting of:
     *
     * <token>a</token>
     * <token>simple</token>
     * <token>list</token>
     * </pre>
     * If the second argument is omitted, the default is the string '&#x20;' (i.e. a space).
     *
     * @param str The string to be split
     * @param pattern The pattern
     *
     * @return A node set of split tokens
     */
    public static NodeList split(String str, String pattern) {

        NodeSet resultSet = new NodeSet();
        resultSet.setShouldCacheNodes(true);

        boolean done = false;
        int fromIndex = 0;
        int matchIndex = 0;
        String token = null;

        while (!done && fromIndex < str.length()) {
            matchIndex = str.indexOf(pattern, fromIndex);
            if (matchIndex >= 0) {
                token = str.substring(fromIndex, matchIndex);
                fromIndex = matchIndex + pattern.length();
            } else {
                done = true;
                token = str.substring(fromIndex);
            }

            Document doc = getDocument();
            synchronized (doc) {
                Element element = doc.createElement("token");
                Text text = doc.createTextNode(token);
                element.appendChild(text);
                resultSet.addNode(element);
            }
        }

        return resultSet;
    }

    /**
     * See above
     */
    public static NodeList split(String str) {
        return split(str, " ");
    }

    /**
     * The str:tokenize function splits up a string and returns a node set of token
     * elements, each containing one token from the string.
     * <p>
     * The first argument is the string to be tokenized. The second argument is a
     * string consisting of a number of characters. Each character in this string is
     * taken as a delimiting character. The string given by the first argument is split
     * at any occurrence of any of these characters. For example:
     * <pre>
     * str:tokenize('2001-06-03T11:40:23', '-T:') gives the node set consisting of:
     *
     * <token>2001</token>
     * <token>06</token>
     * <token>03</token>
     * <token>11</token>
     * <token>40</token>
     * <token>23</token>
     * </pre>
     * If the second argument is omitted, the default is the string '&#x9;&#xA;&#xD;&#x20;'
     * (i.e. whitespace characters).
     * <p>
     * If the second argument is an empty string, the function returns a set of token
     * elements, each of which holds a single character.
     * <p>
     * Note: This one is different from the tokenize extension function in the Xalan
     * namespace. The one in Xalan returns a set of Text nodes, while this one wraps
     * the Text nodes inside the token Element nodes.
     *
     * @param toTokenize The string to be tokenized
     * @param delims The delimiter string
     *
     * @return A node set of split token elements
     */
    public static NodeList tokenize(String toTokenize, String delims) {

        NodeSet resultSet = new NodeSet();

        if (delims != null && delims.length() > 0) {
            StringTokenizer lTokenizer = new StringTokenizer(toTokenize, delims);

            Document doc = getDocument();
            synchronized (doc) {
                while (lTokenizer.hasMoreTokens()) {
                    Element element = doc.createElement("token");
                    element.appendChild(doc.createTextNode(lTokenizer.nextToken()));
                    resultSet.addNode(element);
                }
            }
        }
        // If the delimiter is an empty string, create one token Element for
        // every single character.
        else {

            Document doc = getDocument();
            synchronized (doc) {
                for (int i = 0; i < toTokenize.length(); i++) {
                    Element element = doc.createElement("token");
                    element.appendChild(doc.createTextNode(toTokenize.substring(i, i + 1)));
                    resultSet.addNode(element);
                }
            }
        }

        return resultSet;
    }

    /**
     * See above
     */
    public static NodeList tokenize(String toTokenize) {
        return tokenize(toTokenize, " \t\n\r");
    }

    /**
    * @return an instance of DOM Document
     */
    private static Document getDocument() {
        try {
            if (System.getSecurityManager() == null) {
                return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
            } else {
                return DocumentBuilderFactory.newInstance(JDK_DEFAULT_DOM, null).newDocumentBuilder().newDocument();
            }
        } catch (ParserConfigurationException pce) {
            throw new com.sun.org.apache.xml.internal.utils.WrappedRuntimeException(pce);
        }
    }
}
